
package com.segment.dcore.base.utils;

import org.slf4j.MDC;

import com.segment.dcore.base.component.ContextIDGenerator;
import com.segment.dcore.base.constants.ServiceConstants;

/**
 * 调用链数据设置工具类
 *
 * Created by huapeng.hhp on 2017/2/26.
 */
public class ContextIdUtil {
	/** 调用url */
	private final static ThreadLocal<String> entry = new ThreadLocal<String>();
	/** 调用主键 */
	private final static ThreadLocal<String> requsetId = new ThreadLocal<String>();
	/** 调用深度 */
	private final static ThreadLocal<Integer> depth = new ThreadLocal<Integer>();
	/** 调用排序 */
	private final static ThreadLocal<Integer> order = new ThreadLocal<Integer>();

	/** 调用业务逻辑类 */
	public static abstract class RequestInvoke<T> {
		// 调用业务逻辑
		public abstract T doInvoke(final String requestId, final Integer depth, final Integer order);
	}

	/**
	 * 如果没有调用主键则生成，调用深度从1开始递增
	 * 
	 * @param originalId
	 * @param originalDepth
	 * @param originalOrder
	 * @param contextIDGenerator
	 * @param invoke
	 * @param <T>
	 * @return
	 */
	public static <T> T ConsumeContextIdAndDepth(String originalId, Integer originalDepth, Integer originalOrder,
			ContextIDGenerator contextIDGenerator, RequestInvoke<T> invoke) {
		try {
			// 如果上下文中不存在originalId，则生成新的originalId
			if (originalId == null) {
				originalId = contextIDGenerator.buildContextId();
				originalDepth = 0;
				originalOrder = 0;
			}
			// 计算requsetId\depth\order,并将其放入日志上下文中
			calRequsetIdDepthOrderToThreadLocal(originalId, originalDepth, originalOrder);
			// 进行业务调用
			return invoke.doInvoke(requsetId.get(), depth.get(), order.get());
		} finally {
			// 深度减1,并将其放入日志上下文中
			depthDec();
			// 消费者端只有0时才表示它是入库，需要清理
			// 否则将会有真正的入口来负责清理
			if (depth.get().equals(0)) {
				// 将requsetId\depth\order从ThreadLocal中移除，并将其从日志上下文中移除
				removeRequsetIdDepthOrderToThreadLocal();
			}
		}
	}

	public static <T> T ProvideContextIdAndDepth(String methodName, String originalId, Integer originalDepth,
			Integer originalOrder, ContextIDGenerator contextIDGenerator, RequestInvoke<T> invoke) {
		try {
			// 设置入口，并将其放到日志上下文里边
			setEntry(methodName);
			// 如果上下文中不存在originalId，则生成新的originalId
			if (originalId == null) {
				originalId = contextIDGenerator.buildContextId();
				originalDepth = 0;
				originalOrder = 0;
			}
			// 计算requsetId\depth\order,并将其放入日志上下文中
			calRequsetIdDepthOrderToThreadLocal(originalId, originalDepth, originalOrder);
			// 进行业务调用
			return invoke.doInvoke(requsetId.get(), depth.get(), order.get());
		} finally {
			// 将入口从ThreadLocal中移除，并将其从日志上下文中移除
			removeEntry();
			// 深度减1,并将其放入日志上下文中
			depthDec();
			if (depth.get().equals(originalDepth)) {
				// 将requsetId\depth\order从ThreadLocal中移除，并将其从日志上下文中移除
				removeRequsetIdDepthOrderToThreadLocal();
			}
		}
	}

	/**
	 * 获取入口名称
	 * 
	 * @return
	 */
	public static String getEntryName() {
		return entry.get();
	}

	/**
	 * 设置入口，并将其放到日志上下文里边
	 * 
	 * @param methodName
	 */
	private static void setEntry(String methodName) {
		entry.set(methodName);
		MDC.put(ServiceConstants.INSIGHT_ENTRY, entry.get());
	}

	/**
	 * 将入口从ThreadLocal中移除，并将其从日志上下文中移除
	 */
	private static void removeEntry() {
		entry.remove();
		MDC.remove(ServiceConstants.INSIGHT_ENTRY);
	}

	/**
	 * 深度减1,并将其放入日志上下文中
	 */
	private static void depthDec() {
		depth.set(depth.get() - 1);
		MDC.put(ServiceConstants.INSIGHT_DEPTH, depth.get().toString());
	}

	/**
	 * 计算requsetId\depth\order,并将其放入日志上下文中
	 * 
	 * @param originalId
	 * @param originalDepth
	 * @param originalOrder
	 */
	private static void calRequsetIdDepthOrderToThreadLocal(String originalId, Integer originalDepth,
			Integer originalOrder) {
		// 如果当前线程不存在，则存入当前线程中
		if (requsetId.get() == null) {
			requsetId.set(originalId);
			depth.set(originalDepth);
			order.set(originalOrder);
		}
		depth.set(depth.get() + 1);
		order.set(order.get() + 1);
		// 将这些信息放到日志上下文里边
		MDC.put(ServiceConstants.CONTEXT_ID, createContextId());
		MDC.put(ServiceConstants.INSIGHT_REQUEST_ID, requsetId.get());
		MDC.put(ServiceConstants.INSIGHT_DEPTH, depth.get().toString());
		MDC.put(ServiceConstants.INSIGHT_ORDER, order.get().toString());
	}

	/**
	 * 创建时间戳
	 *
	 * @return
	 */
	private static String createContextId() {
		String contextId = "[contextId=" + requsetId.get() + "]";
		return contextId;
	}

	/**
	 * 将requsetId\depth\order从ThreadLocal中移除，并将其从日志上下文中移除
	 */
	private static void removeRequsetIdDepthOrderToThreadLocal() {
		requsetId.remove();
		depth.remove();
		order.remove();
		MDC.remove(ServiceConstants.CONTEXT_ID);
		MDC.remove(ServiceConstants.INSIGHT_REQUEST_ID);
		MDC.remove(ServiceConstants.INSIGHT_DEPTH);
		MDC.remove(ServiceConstants.INSIGHT_ORDER);
	}
}
